/*
 * Toedo
 * Copyright (C) 2009-2010 J. Devauchelle and contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

package org.toedo.gui.views;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.UUID;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.ViewPart;
import org.toedo.controllers.DataController;
import org.toedo.controllers.events.EventConstants;
import org.toedo.controllers.events.EventController;
import org.toedo.gui.dialogs.InsertBaseNoteLinkDialog;
import org.toedo.gui.dialogs.InsertLinkDialog;
import org.toedo.gui.translations.Messages;
import org.toedo.logging.Log;
import org.toedo.model.Attachment;
import org.toedo.model.BaseNote;
import org.toedo.model.Tag;
import org.toedo.model.enums.WikiMarkupType;
import org.toedo.utils.ApplicationUtils;
import org.toedo.utils.Constants;
import org.toedo.utils.WikiUtils;
import org.toedo.Activator;

/**
 * Base class for note viewers. Provide a content section, with the switch process between editing and viewing modes, a details section,
 * an attachments section, and basic update mechanism.
 *
 */
public abstract class BaseNoteViewPart extends ViewPart implements ISaveablePart{
	
	protected Composite mParent;
	protected FormToolkit mToolkit;
	protected Form mForm;
	
	private SashForm mSashContainer;
	
	private Section mContentSection;
	
	private Text mContentText;
	private Browser mBrowser;
	
	private Section mAttachmentsSection;
	private FormText mAttachmentsText1;
	private FormText mAttachmentsText2;
	
	protected Label mTagsList;
	
	private ToolItem mSwitchModeToolItem;
	
	private ToolItem mInsertBoldToolItem;
	private ToolItem mInsertItalicToolItem;
	
	private ToolItem mInsertH1ToolItem;
	private ToolItem mInsertH2ToolItem;
	private ToolItem mInsertH3ToolItem;
	private ToolItem mInsertH4ToolItem;
	
	private ToolItem mInsertLinkToolItem;
	private ToolItem mInsertTaskLinkToolItem;
	private ToolItem mInsertNoteLinkToolItem;
	
	private boolean mIsDirty = false;
	private boolean mUpdatingContent = true;
	private boolean mProcessBrowserEvents = true;	
	private boolean mIsEditMode = false;
	
	private IAction mDefaultCutAction;
	private IAction mDefaultCopyAction;
	private IAction mDefaultPasteAction;
	
	private Image mEditImage;
	
	private Image mBoldImage;
	private Image mItalicImage;
	
	private Image mH1Image;
	private Image mH2Image;
	private Image mH3Image;
	private Image mH4Image;
	
	private Image mLinkImage;
	private Image mTaskLinkImage;
	private Image mNoteLinkImage;
	
	protected IPartListener2 mFocusListener;	
	
	private Point mCurrentPosition = new Point(0, 0);
	
	private static final String TASKLINK_SUBPATTERN = Constants.TASK_LINK_WIKI_PREFIX + "%s"; //$NON-NLS-1$
	private static final String NOTELINK_SUBPATTERN = Constants.NOTE_LINK_WIKI_PREFIX + "%s"; //$NON-NLS-1$
	
	public BaseNoteViewPart() {
		Activator.getDefault();
		mEditImage = Activator.getImageDescriptor("/icons/edit-16.png").createImage(); //$NON-NLS-1$
		
		mBoldImage = Activator.getImageDescriptor("/icons/insert-bold-16.png").createImage(); //$NON-NLS-1$
		mItalicImage = Activator.getImageDescriptor("/icons/insert-italic-16.png").createImage(); //$NON-NLS-1$
		
		mH1Image = Activator.getImageDescriptor("/icons/insert-h1-16.png").createImage(); //$NON-NLS-1$
		mH2Image = Activator.getImageDescriptor("/icons/insert-h2-16.png").createImage(); //$NON-NLS-1$
		mH3Image = Activator.getImageDescriptor("/icons/insert-h3-16.png").createImage(); //$NON-NLS-1$
		mH4Image = Activator.getImageDescriptor("/icons/insert-h4-16.png").createImage(); //$NON-NLS-1$
		
		mLinkImage = Activator.getImageDescriptor("/icons/insert-link-16.png").createImage(); //$NON-NLS-1$
		mTaskLinkImage = Activator.getImageDescriptor("/icons/insert-tasklink-16.png").createImage(); //$NON-NLS-1$
		mNoteLinkImage = Activator.getImageDescriptor("/icons/insert-notelink-16.png").createImage(); //$NON-NLS-1$
	}
	
	/**
	 * Build the form headers, e.g. initialize the toolkit, the main forms, and set default title and icon.
	 * @param baseNote
	 * @param defaultViewImage
	 */
	protected void buildFormHeader(final BaseNote baseNote, Image defaultViewImage) {
		setPartName(baseNote.getTitle());
		
		mToolkit = new FormToolkit(mParent.getDisplay());
		mForm = mToolkit.createForm(mParent);
		mForm.setText(baseNote.getTitle());
		mForm.setImage(defaultViewImage);
		
		mToolkit.decorateFormHeading(mForm);
		
		mForm.addMessageHyperlinkListener(new HyperlinkAdapter() {
			@Override
			public void linkActivated(HyperlinkEvent e) {
				switchEditMode(baseNote);
			}
		});
	
		GridLayout layout = new GridLayout(1, true);
		mForm.getBody().setLayout(layout);
	}
	
	private void setEditToolbarEnabled(boolean enabled) {
		mInsertBoldToolItem.setEnabled(enabled);
		mInsertItalicToolItem.setEnabled(enabled);
		
		mInsertH1ToolItem.setEnabled(enabled);
		mInsertH2ToolItem.setEnabled(enabled);
		mInsertH3ToolItem.setEnabled(enabled);
		mInsertH4ToolItem.setEnabled(enabled);
		
		mInsertLinkToolItem.setEnabled(enabled);
		mInsertTaskLinkToolItem.setEnabled(enabled);
		mInsertNoteLinkToolItem.setEnabled(enabled);
	}
	
	/**
	 * Insert a pattern in the editor. The current selection is applied to the pattern.
	 * If the current selection is empty, move the caret back of deltaIfEmpty position.
	 * 
	 * Ex: With a pattern like *%s*, deltaIfEmpty should be 1, in order to move the caret back
	 * between the two *, allowing the user to type its text.
	 * @param insertionPattern
	 * @param deltaIfEmpty
	 */
	private void doInsert(String insertionPattern, int deltaIfEmpty) {
		int initialSelectionCount = mContentText.getSelectionCount(); 
		mContentText.insert(String.format(insertionPattern, mContentText.getSelectionText()));
		
		if (initialSelectionCount == 0) {
			mContentText.setSelection(mContentText.getSelection().x - deltaIfEmpty);
		}
	}
	
	/**
	 * Insert a link in the editor.
	 * @param title
	 * @param url
	 */
	private void doInsertLink(String title, String url) {
		mContentText.insert(WikiUtils.getFormatedLink(DataController.getInstance().getProject().getMarkupType(), title, url));
	}
	
	private ToolItem createToolItem(ToolBar parent, Image image, String toolTip) {
		ToolItem result = new ToolItem(parent, SWT.NONE);
		result.setImage(image);
		result.setToolTipText(toolTip);
		
		return result;
	}	
	
	/**
	 * Build the note content toolbar.
	 * @param baseNote
	 */
	private void buildToolBar(final BaseNote baseNote) {
		ToolBar contentSectionToolBar = new ToolBar(mContentSection, SWT.FLAT | SWT.HORIZONTAL);
		
		mInsertBoldToolItem = createToolItem(contentSectionToolBar, mBoldImage, Messages.BaseNoteViewPart_BoldToolTip);
		mInsertBoldToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
				
				doInsert(WikiUtils.getBoldPattern(markupType), WikiUtils.getBoldDelta(markupType));
			}
		});
		
		mInsertItalicToolItem = createToolItem(contentSectionToolBar, mItalicImage, Messages.BaseNoteViewPart_ItalicToolTip);
		mInsertItalicToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
				
				doInsert(WikiUtils.getItalicPattern(markupType), WikiUtils.getItalicDelta(markupType));
			}
		});
		
		new ToolItem(contentSectionToolBar, SWT.SEPARATOR);
		
		mInsertH1ToolItem = createToolItem(contentSectionToolBar, mH1Image, Messages.BaseNoteViewPart_H1ToolTip);
		mInsertH1ToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
				
				doInsert(WikiUtils.getH1Pattern(markupType), WikiUtils.getH1Delta(markupType));
			}
		});
		
		mInsertH2ToolItem = createToolItem(contentSectionToolBar, mH2Image, Messages.BaseNoteViewPart_H2ToolTip);
		mInsertH2ToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
				
				doInsert(WikiUtils.getH2Pattern(markupType), WikiUtils.getH2Delta(markupType));
			}
		});
		
		mInsertH3ToolItem = createToolItem(contentSectionToolBar, mH3Image, Messages.BaseNoteViewPart_H3ToolTip);
		mInsertH3ToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
				
				doInsert(WikiUtils.getH3Pattern(markupType), WikiUtils.getH3Delta(markupType));
			}
		});
		
		mInsertH4ToolItem = createToolItem(contentSectionToolBar, mH4Image, Messages.BaseNoteViewPart_H4ToolTip);
		mInsertH4ToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
				
				doInsert(WikiUtils.getH4Pattern(markupType), WikiUtils.getH4Delta(markupType));
			}
		});
		
		new ToolItem(contentSectionToolBar, SWT.SEPARATOR);
		
		mInsertLinkToolItem = createToolItem(contentSectionToolBar, mLinkImage, Messages.BaseNoteViewPart_InsertLinkToolTip);
		mInsertLinkToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				InsertLinkDialog dialog = new InsertLinkDialog(mParent.getShell());
				int result = dialog.open();
				if (result == Dialog.OK) {
					doInsertLink(dialog.getTitle(), dialog.getUrl());
				}
			}
		});
		mInsertTaskLinkToolItem = createToolItem(contentSectionToolBar, mTaskLinkImage, Messages.BaseNoteViewPart_InsertTaskLinkToolTip);
		mInsertTaskLinkToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				InsertBaseNoteLinkDialog dialog = new InsertBaseNoteLinkDialog(mParent.getShell(), 0);
				int result = dialog.open();
				if (result == Dialog.OK) {
					doInsertLink(dialog.getTitle(), String.format(TASKLINK_SUBPATTERN, dialog.getId()));
				}
			}
		});
		mInsertNoteLinkToolItem = createToolItem(contentSectionToolBar, mNoteLinkImage, Messages.BaseNoteViewPart_InsertNoteLinkToolTip);
		mInsertNoteLinkToolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				InsertBaseNoteLinkDialog dialog = new InsertBaseNoteLinkDialog(mParent.getShell(), 1);
				int result = dialog.open();
				if (result == Dialog.OK) {
					doInsertLink(dialog.getTitle(), String.format(NOTELINK_SUBPATTERN, dialog.getId()));
				}
			}
		});
		
		new ToolItem(contentSectionToolBar, SWT.SEPARATOR);
		
		mSwitchModeToolItem = new ToolItem(contentSectionToolBar, SWT.CHECK);
		mSwitchModeToolItem.setImage(mEditImage);
		mSwitchModeToolItem.setToolTipText(Messages.BaseNoteViewPart_SwitchEditMode);
		mSwitchModeToolItem.addSelectionListener(new SelectionAdapter(){
			public void widgetSelected(SelectionEvent e) {
				switchEditMode(baseNote);
			}
		});						
		
		mContentSection.setTextClient(contentSectionToolBar);
		
		setEditToolbarEnabled(false);
	}
	
	/**
	 * Build the content section, e.g. the editor and the browser.
	 * @param baseNote
	 */
	protected void buildContentSection(final BaseNote baseNote) {
		mContentSection = mToolkit.createSection(mForm.getBody(), 
				 Section.TITLE_BAR | Section.EXPANDED);
		mContentSection.setText(Messages.TaskView_CententSectionTitle);
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
		mContentSection.setLayoutData(gd);
		
		buildToolBar(baseNote);
		
		Composite contentComposite = mToolkit.createComposite(mContentSection);
		contentComposite.setLayout(new FillLayout());
					
		mSashContainer = new SashForm(contentComposite, SWT.HORIZONTAL);
		mSashContainer.setLayout(new FillLayout());
		mSashContainer.setSashWidth(0);
		
		Composite leftPanel = new Composite(mSashContainer, SWT.NONE);
		leftPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		GridLayout leftGL = new GridLayout(1, false);
		leftPanel.setLayout(leftGL);
		
		mBrowser = new Browser(leftPanel, SWT.BORDER);
		mBrowser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		mBrowser.addLocationListener(new LocationListener() {
			@Override
			public void changed(LocationEvent event) { }
			@Override
			public void changing(LocationEvent event) {
				
				if (!event.location.equals("about:blank")) { //$NON-NLS-1$
					if (mProcessBrowserEvents) {
						event.doit = false;

						if (event.location.contains(Constants.INVALID_SMART_LINK)) {
							
							ApplicationUtils.notifyUser(Messages.BaseNoteViewPart_InvalidLinkTitle, Messages.BaseNoteViewPart_InvalidLinkMessage);
							
						} else if (event.location.startsWith(Constants.TASK_LINK_WIKI_PREFIX)) {
							
							String taskId = event.location.substring(event.location.indexOf(":") + 1); //$NON-NLS-1$
							DataController.getInstance().getProject().openTaskById(UUID.fromString(taskId));
							
						} else if (event.location.startsWith(Constants.NOTE_LINK_WIKI_PREFIX)) {
							
							String noteId = event.location.substring(event.location.indexOf(":") + 1); //$NON-NLS-1$
							DataController.getInstance().getProject().openNoteById(UUID.fromString(noteId));
							
						} else {
							
							ApplicationUtils.openLinkInBrowser(event.location, true);
							
						}
					}
				}
			}				
		});
		
		Composite rightPanel = new Composite(mSashContainer, SWT.NONE);
		rightPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		GridLayout rightGL = new GridLayout(1, false);
		rightPanel.setLayout(rightGL);
		
		mContentText = mToolkit.createText(rightPanel, null, SWT.BORDER | SWT.MULTI | SWT.WRAP | SWT.V_SCROLL);			
		mContentText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		
		mContentText.addModifyListener(new ModifyListener() {
			@Override
			public void modifyText(ModifyEvent e) {
				if (mUpdatingContent) {					
					DataController.getInstance().getProject().setBaseNoteContent(baseNote, mContentText.getText());
					
					if  (!mIsDirty) {
						// In order to update the "*" in title bar.
						EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_BASENOTE_CONTENT_CHANGE, null, null);
					}
					
					setDirty(true);
				}
			}				
		});
		
		mContentText.addFocusListener(new FocusListener() {
			@Override
			public void focusGained(FocusEvent e) {
				registerStandardActions(mContentText);					
			}
			@Override
			public void focusLost(FocusEvent e) {
				unregisterStandardActions();
			}
		});
		
		mContentText.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if ((e.stateMask & SWT.CTRL) != 0) {
					
					WikiMarkupType markupType = DataController.getInstance().getProject().getMarkupType();
					
					switch (e.keyCode) {
					case 'b': doInsert(WikiUtils.getBoldPattern(markupType), WikiUtils.getBoldDelta(markupType)); break;
					case 'i': doInsert(WikiUtils.getItalicPattern(markupType), WikiUtils.getItalicDelta(markupType)); break;
					}
				}				
			}
		});
		
		mContentSection.setClient(contentComposite);
		
		mSashContainer.setWeights(new int[] {100, 0});
	}
	
	/**
	 * Build the attachments section.
	 * @param baseNote
	 */
	protected void buildAttachmentSection(final BaseNote baseNote) {
		mAttachmentsSection = mToolkit.createSection(mForm.getBody(), 
				 Section.TITLE_BAR | Section.TWISTIE);
		mAttachmentsSection.setText("Attachements"); //$NON-NLS-1$
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		mAttachmentsSection.setLayoutData(gd);
		
		Composite attachmentsComposite = mToolkit.createComposite(mAttachmentsSection);
		attachmentsComposite.setLayout(new GridLayout(2, true));
		
		mAttachmentsText1 = mToolkit.createFormText(attachmentsComposite, true);
		mAttachmentsText1.addHyperlinkListener(new HyperlinkAdapter() {
			@Override
			public void linkActivated(HyperlinkEvent e) {					
				processAttachmentClick(e, baseNote);
			}
		});
		
		mAttachmentsText2 = mToolkit.createFormText(attachmentsComposite, true);
		mAttachmentsText2.addHyperlinkListener(new HyperlinkAdapter() {
			@Override
			public void linkActivated(HyperlinkEvent e) {
				processAttachmentClick(e, baseNote);
			}
		});
		
		mAttachmentsSection.setClient(attachmentsComposite);
	}
	
	/**
	 * Allow to register a Text component with the standards Cut, Copy and Paste menu item and shortcuts.
	 * This is usually called when the Text component get the focus.
	 * @param text
	 */
	private void registerStandardActions(final Text text) {
		mDefaultCutAction = new Action() {
			public void run() {
				text.cut();
			}
		};
		
		mDefaultCopyAction = new Action() {
			public void run() {
				text.copy();
			}
		};
		
		mDefaultPasteAction = new Action() {
			public void run() {
				text.paste();
			}
		};
		
		IActionBars actionBars = getViewSite().getActionBars();
		actionBars.clearGlobalActionHandlers();
		actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), mDefaultCutAction);
		actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), mDefaultCopyAction);
		actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), mDefaultPasteAction);
		actionBars.updateActionBars();
	}
	
	/**
	 * Unregister all components linked to the standards Cut, Copy and Paste menu items and shortcuts.
	 * This is usually done when a Text component lost the focus.
	 */
	private void unregisterStandardActions() {
		IActionBars actionBars = getViewSite().getActionBars();
		actionBars.clearGlobalActionHandlers();
		actionBars.updateActionBars();
	}
	
	/**
	 * Perform the switch between the editor and the browser.
	 * 	- Setup UI;
	 *  - Setup visibility of the toolbar edit buttons;
	 *  - Update content;
	 *  - ...
	 * @param baseNote
	 */
	public void switchEditMode(BaseNote baseNote) {		
		if (mIsEditMode) {
			mCurrentPosition = mContentText.getSelection();
			updateDisplay(baseNote);
			mSashContainer.setWeights(new int[] {100, 0});
			setEditToolbarEnabled(false);
			mContentSection.setFocus();
		} else {
			mSashContainer.setWeights(new int[] {0, 100});
			setEditToolbarEnabled(true);
			mContentText.setFocus();
			updateDisplay(baseNote);
			mContentText.setSelection(mCurrentPosition);
		}
		mIsEditMode = !mIsEditMode;
		mSwitchModeToolItem.setSelection(mIsEditMode);
	}
	
	/**
	 * This method is called by the "Switch edit mode" menu item.
	 * Subclasses must override this in order to call switchEditMode(BaseNote baseNote)
	 * with the real BaseNote they own.
	 */
	public abstract void switchEditMode();
	
	/**
	 * Update the view:
	 * 	- Set titles;
	 *  - Set contents;
	 *  - Update tags;	
	 * @param baseNote
	 */
	protected void updateDisplay(BaseNote baseNote) {
		setPartName(baseNote.getTitle());		
		mForm.setText(baseNote.getTitle());
		
		String rawContent = baseNote.getContent();
		String rawHtml = baseNote.getHtmlContent();
		
		if ((!mIsEditMode) &&
				(((rawContent == null) ||
				 (rawContent.isEmpty())))) {
			mForm.setMessage(Messages.BaseNoteViewPart_EmptyMessage, IMessageProvider.INFORMATION);
		} else {
			mForm.setMessage(null);
		}
		
		mUpdatingContent = false;
		mContentText.setText(rawContent);
		mBrowser.setText(rawHtml);
		mUpdatingContent = true;
		
		boolean first = true;
		StringBuilder sb = new StringBuilder();
		Iterator<Tag> iter = baseNote.getTagsList().iterator();
		while (iter.hasNext()) {
			if (first) {
				sb.append(iter.next().getName());
				first = false;
			} else {
				sb.append(", " + iter.next().getName()); //$NON-NLS-1$
			}
		}
		mTagsList.setText(sb.toString());
		
		updateAttachmentDisplay(baseNote);
	}
	
	/**
	 * Update the attachement section UI.
	 * @param baseNote
	 */
	private void updateAttachmentDisplay(BaseNote baseNote) {
		StringBuilder sb1 = new StringBuilder();
		sb1.append("<form>"); //$NON-NLS-1$
		
		StringBuilder sb2 = new StringBuilder();
		sb2.append("<form>"); //$NON-NLS-1$
		
		String line;
		int count = 0;
		
		for (Attachment attachment : baseNote.getAttachmentList()) {
			line = String.format("<li><a href=\"%s\">%s</a></li>", attachment.getId(), attachment.getTitle()); //$NON-NLS-1$
			
			if ((count % 2) == 0) {
				sb1.append(line);
			} else {
				sb2.append(line);
			}
			count++;
		}
		
		sb1.append("</form>"); //$NON-NLS-1$
		sb2.append("</form>"); //$NON-NLS-1$
		
		mAttachmentsText1.setText(sb1.toString(), true, true);
		mAttachmentsText2.setText(sb2.toString(), true, true);
		
		mAttachmentsSection.setText(String.format(Messages.TaskView_AttachmentSectionTitle, Integer.toString(baseNote.getAttachmentsMap().size())));
		mAttachmentsSection.layout(true);
	}
	
	/**
	 * Process a click on an attachment. Depending on the attachement type,
	 * the document is openned or the link is browsed.
	 * @param e
	 * @param note
	 */
	protected void processAttachmentClick(HyperlinkEvent e, BaseNote note) {
		Attachment attachment = note.getAttachmentById(UUID.fromString(e.getHref().toString()));
		
		if (attachment != null) {	
			
			switch (attachment.getType()) {
			case EnFile : {
				try {									
					File file = new File(attachment.getLink());
					if (file.exists()) {
						if (Util.isWindows()) {
							/* Again some specific Windows stuff...
							 * AFAIK, Desktop.getDesktop().open() _may_ works under Windows, if files associations in the registry
							 * are well written, witch does not seems to be the case all the time.
							 * See http://forums.java.net/jive/thread.jspa?messageID=198634
							 */
							Runtime.getRuntime().exec("cmd /c \"" + file.getAbsolutePath() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
						} else {
							Desktop.getDesktop().open(file);
						}
					} else {
						String message = String.format("Unable to open %s (%s): file does not exists.", attachment.getTitle(), attachment.getLink()); //$NON-NLS-1$
						Log.getInstance(TaskView.class).warn(message);
						
						ApplicationUtils.notifyUser("Unable to open link", message); //$NON-NLS-1$
					}
				} catch (IOException ioe) {
					String message = String.format("Unable to open %s (%s): IO Exception (%s).", attachment.getTitle(), attachment.getLink(), ioe.getMessage()); //$NON-NLS-1$
					Log.getInstance(TaskView.class).warn(message);
					
					ApplicationUtils.notifyUser("Unable to open link", message); //$NON-NLS-1$
				}
				break;
			}
			case EnUrl : {
				try {
					
					URI uri = new URI(attachment.getLink());
					Desktop.getDesktop().browse(uri);
					
				} catch (IOException ioe) {
					String message = String.format("Unable to open %s (%s): IO Exception (%s).", attachment.getTitle(), attachment.getLink(), ioe.getMessage()); //$NON-NLS-1$
					Log.getInstance(TaskView.class).warn(message);
					
					ApplicationUtils.notifyUser("Unable to open link", message); //$NON-NLS-1$
				} catch (URISyntaxException urise) {
					String message = String.format("Unable to open %s (%s): URISyntax Exception (%s).", attachment.getTitle(), attachment.getLink(), urise.getMessage()); //$NON-NLS-1$
					Log.getInstance(TaskView.class).warn(message);
					
					ApplicationUtils.notifyUser("Unable to open link", message); //$NON-NLS-1$
				}
				break;
			}
			}
			
		} else  {
			String message = String.format("Unable to open %s: attachment not found.", e.getHref().toString()); //$NON-NLS-1$
			Log.getInstance(TaskView.class).warn(message);
			
			ApplicationUtils.notifyUser("Unable to open link", message); //$NON-NLS-1$
		}
	}
	
	public void dispose() {
		mEditImage.dispose();
		
		mBoldImage.dispose();
		mItalicImage.dispose();
		
		mH1Image.dispose();
		mH2Image.dispose();
		mH3Image.dispose();
		mH4Image.dispose();
		
		mLinkImage.dispose();
		mTaskLinkImage.dispose();
		mNoteLinkImage.dispose();
		
		mToolkit.dispose();
		
		this.getViewSite().getPage().removePartListener(mFocusListener);
	}
	
	/**
	 * Setup dirty state of the view, and notify its change.
	 * @param value
	 */
	protected void setDirty(boolean value) {
		mIsDirty = value;
		firePropertyChange(ISaveablePart.PROP_DIRTY);
	}
	
	@Override
	public void doSave(IProgressMonitor monitor) { }

	@Override
	public void doSaveAs() { }

	@Override
	public boolean isDirty() {
		return mIsDirty;
	}

	@Override
	public boolean isSaveAsAllowed() {
		return false;
	}

	@Override
	public boolean isSaveOnCloseNeeded() {
		return false;
	}


}
